﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.Serialization;

namespace Skywave.Data
{
    [DataContract]
    public enum ChartDataGeneratorAxisDateTimePointTextDisplayTypes
    {
        [EnumMember]
        Period = 1,
        [EnumMember]
        Start = 2,
        [EnumMember]
        End = 3
    }

    [DataContract]
    public enum ChartDataGeneratorAxisDateTimeDatePortions
    {
        [EnumMember]
        Year = 1,
        [EnumMember]
        Month = 2,
        [EnumMember]
        Day = 4,
        [EnumMember]
        DayOfWeek = 8
    }

    [DataContract]
    public enum ChartDataGeneratorAxisDateTimePeriodTypes
    {
        [EnumMember]
        Custom = 1,
        [EnumMember]
        AutoByCount = 2,
        [EnumMember]
        AutoByDatePortions = 3
    }

    [DataContract]
    public class ChartDataGeneratorAxisDateTime : ChartDataGeneratorAxis
    {

        private string _ColumnDesignId;
        /// <summary>
        /// ColumnDesignId Property
        /// </summary>
        [DataMember]
        public string ColumnDesignId
        {
            get { return _ColumnDesignId; }
            set { _ColumnDesignId = value; }
        }

        private string _ColumnSQL;
        /// <summary>
        /// ColumnName Property
        /// </summary>
        [DataMember]
        public string ColumnSQL
        {
            get { return _ColumnSQL; }
            set { _ColumnSQL = value; }
        }

        private DateTime? _Start;
        /// <summary>
        /// Start datetime in UTC
        /// </summary>
        [DataMember]
        public DateTime? Start
        {
            get { return _Start; }
            set { _Start = value; }
        }

        private DateTime? _End;
        /// <summary>
        /// End datetime in UTC
        /// </summary>
        [DataMember]
        public DateTime? End
        {
            get { return _End; }
            set { _End = value; }
        }

        private ChartDataGeneratorAxisDateTimePeriodTypes _PeriodType = ChartDataGeneratorAxisDateTimePeriodTypes.AutoByDatePortions;
        /// <summary>
        /// PeriodType Property
        /// </summary>
        [DataMember]
        public ChartDataGeneratorAxisDateTimePeriodTypes PeriodType
        {
            get { return _PeriodType; }
            set { _PeriodType = value; }
        }

        private TimeSpan? _Period_Custom = null;
        /// <summary>
        /// Period Property
        /// </summary>
        [DataMember]
        public TimeSpan? Period_Custom
        {
            get { return _Period_Custom; }
            set { _Period_Custom = value; }
        }

        private int? _Period_AutoByCount = null;
        /// <summary>
        /// AutoPeriodCount Property
        /// </summary>
        [DataMember]
        public int? Period_AutoByCount
        {
            get { return _Period_AutoByCount; }
            set { _Period_AutoByCount = value; }
        }

        private List<ChartDataGeneratorAxisDateTimeDatePortions> _Period_AutoByDatePortions = new List<ChartDataGeneratorAxisDateTimeDatePortions>();
        /// <summary>
        /// Period_AutoByDatePortion Property
        /// </summary>
        [DataMember]
        public List<ChartDataGeneratorAxisDateTimeDatePortions> Period_AutoByDatePortions
        {
            get { return _Period_AutoByDatePortions; }
            set { _Period_AutoByDatePortions = value; }
        }

        private bool _Period_AutoByDatePortion_ConvertToLocalDate = true;
        /// <summary>
        /// Period_AutoByDatePortion_ConvertToLocalDate Property
        /// </summary>
        [DataMember]
        public bool Period_AutoByDatePortion_ConvertToLocalDate
        {
            get { return _Period_AutoByDatePortion_ConvertToLocalDate; }
            set { _Period_AutoByDatePortion_ConvertToLocalDate = value; }
        }

        private bool _AutoInfinity_Start = false;
        /// <summary>
        /// AutoInfinity_Start Property
        /// </summary>
        [DataMember]
        public bool AutoInfinity_Start
        {
            get { return _AutoInfinity_Start; }
            set { _AutoInfinity_Start = value; }
        }

        private bool _AutoInfinity_End = false;
        /// <summary>
        /// AutoInfinity_End Property
        /// </summary>
        [DataMember]
        public bool AutoInfinity_End
        {
            get { return _AutoInfinity_End; }
            set { _AutoInfinity_End = value; }
        }

        private DateTimeHelperCalendars? _PointText_Converted_Calendar;
        /// <summary>
        /// PointText_Converted_Calendar Property
        /// </summary>
        [DataMember]
        public DateTimeHelperCalendars? PointText_Converted_Calendar
        {
            get { return _PointText_Converted_Calendar; }
            set { _PointText_Converted_Calendar = value; }
        }

        private bool _PointText_Converted_Start_IncludeTime = false;
        /// <summary>
        /// PointText_Converted_Start_IncludeTime Property
        /// </summary>
        [DataMember]
        public bool PointText_Converted_Start_IncludeTime
        {
            get { return _PointText_Converted_Start_IncludeTime; }
            set { _PointText_Converted_Start_IncludeTime = value; }
        }

        private DateTimeHelperDisplayFormats _PointText_Converted_Start_Format = DateTimeHelperDisplayFormats.ShortDate;
        /// <summary>
        /// PointText_Converted_Start_Format Property
        /// </summary>
        [DataMember]
        public DateTimeHelperDisplayFormats PointText_Converted_Start_Format
        {
            get { return _PointText_Converted_Start_Format; }
            set { _PointText_Converted_Start_Format = value; }
        }

        private bool _PointText_Converted_End_IncludeTime = false;
        /// <summary>
        /// PointText_Converted_End_IncludeTime Property
        /// </summary>
        [DataMember]
        public bool PointText_Converted_End_IncludeTime
        {
            get { return _PointText_Converted_End_IncludeTime; }
            set { _PointText_Converted_End_IncludeTime = value; }
        }

        private DateTimeHelperDisplayFormats _PointText_Converted_End_Format = DateTimeHelperDisplayFormats.ShortDate;
        /// <summary>
        /// PointText_Converted_End_Format Property
        /// </summary>
        [DataMember]
        public DateTimeHelperDisplayFormats PointText_Converted_End_Format
        {
            get { return _PointText_Converted_End_Format; }
            set { _PointText_Converted_End_Format = value; }
        }

        private string _PointText_NotConverted_FormatString = null;
        /// <summary>
        /// PointText_NotConverted_FormatString Property
        /// </summary>
        [DataMember]
        public string PointText_NotConverted_FormatString
        {
            get { return _PointText_NotConverted_FormatString; }
            set { _PointText_NotConverted_FormatString = value; }
        }

        private ChartDataGeneratorAxisDateTimePointTextDisplayTypes _PointText_DisplayType = ChartDataGeneratorAxisDateTimePointTextDisplayTypes.Period;
        /// <summary>
        /// PointText_DisplayType Property
        /// </summary>
        [DataMember]
        public ChartDataGeneratorAxisDateTimePointTextDisplayTypes PointText_DisplayType
        {
            get { return _PointText_DisplayType; }
            set { _PointText_DisplayType = value; }
        }

        Dictionary<int, string> casesTexts = new Dictionary<int, string>();
        int? colWithDayOfWeekValue = null;
        public override ChartDataGeneratorAxisCases GetCases(string prefixForNames, ChartDataGenerator parent)
        {
            string column_Primary_Name = parent.Filter_PrimaryColumn_FullName;
            if (Period_Custom == null && Period_AutoByCount == null && MaxCaseCount == null && (Period_AutoByDatePortions == null || Period_AutoByDatePortions.Count == 0))
                throw new ArgumentException("All 'Period_Custom', 'Period_AutoByDatePortion', 'Period_AutoByCount' and 'MaxCaseCount' can not be null.");
            if (Start == null && PeriodType == ChartDataGeneratorAxisDateTimePeriodTypes.AutoByCount)
                throw new ArgumentException("You are using 'AutoByCount' period type, but didn't specified 'Start', there is no way to auto generate.");
            if (PeriodType == ChartDataGeneratorAxisDateTimePeriodTypes.AutoByCount && MaxCaseCount == null && Period_AutoByCount == null)
                throw new ArgumentException("Both 'MaxCaseCount' and 'Period_AutoByCount' can not be null when using 'AutoByCount' period type.");

            ChartDataGeneratorAxisCases r = new ChartDataGeneratorAxisCases();
            string s1, colSQL;
            //
            colSQL = ColumnSQL;
            //
            r.JoinClauses = JoinClauses.Values.ToList();
            foreach (var fe1 in JoinClauses)
            {
                s1 = string.Format("{0}t_{1}", prefixForNames, fe1.Key);
                r.JoinClauses = (from x1 in r.JoinClauses select x1.Replace(fe1.Key, s1)).ToList();
                colSQL = colSQL.Replace(fe1.Key, s1);
            }
            //
            r.Params = Params.Values.ToList();
            foreach (var fe1 in Params)
            {
                s1 = string.Format("{0}p_{1}", prefixForNames, fe1.Key);
                r.Params = (from x1 in r.Params select x1.Replace(fe1.Key, s1)).ToList();
                colSQL = colSQL.Replace(fe1.Key, s1);
            }
            //
            List<DataStructure.Pair<DateTime?, DateTime?>> periods = new List<DataStructure.Pair<DateTime?, DateTime?>>();
            DateTime nowUTC = DateTime.UtcNow;
            DateTime d1, d2;
            int cCase = 1;
            DataStructure.ListHelper<string> listHelper1;
            string columnValueName = prefixForNames + "c_{0}";
            //
            if (PeriodType == ChartDataGeneratorAxisDateTimePeriodTypes.AutoByCount || PeriodType == ChartDataGeneratorAxisDateTimePeriodTypes.Custom)
            {
                columnValueName = string.Format(columnValueName, 1);
                //
                if (Start != null) //Start is not null, so we will add starting from start
                {
                    TimeSpan periodToUse;
                    if (PeriodType == ChartDataGeneratorAxisDateTimePeriodTypes.AutoByCount)
                    {

                        int divisionCount;
                        if (MaxCaseCount != null && Period_AutoByCount != null)
                            divisionCount = Math.Min(MaxCaseCount.Value, Period_AutoByCount.Value);
                        else if (MaxCaseCount != null)
                            divisionCount = MaxCaseCount.Value;
                        else
                            divisionCount = Period_AutoByCount.Value;
                        //
                        periodToUse = new TimeSpan((((End == null) ? nowUTC : End.Value) - Start.Value).Ticks / divisionCount);
                    }
                    else
                        periodToUse = Period_Custom.Value;
                    //
                    d1 = Start.Value;
                    while ((MaxCaseCount == null || cCase <= MaxCaseCount.Value) && ((End == null) ? d1 <= nowUTC : d1 <= End.Value))
                    {
                        d2 = d1.Add(periodToUse);
                        periods.Add(new DataStructure.Pair<DateTime?, DateTime?>(d1, d2));
                        d1 = d2;
                        cCase++;
                    }
                }
                else //Start is null, so we will substract starting from end
                {
                    TimeSpan periodToUse;
                    if (PeriodType == ChartDataGeneratorAxisDateTimePeriodTypes.AutoByCount)
                    {
                        int divisionCount;
                        if (MaxCaseCount != null && Period_AutoByCount != null)
                            divisionCount = Math.Min(MaxCaseCount.Value, Period_AutoByCount.Value);
                        else if (MaxCaseCount != null)
                            divisionCount = MaxCaseCount.Value;
                        else
                            divisionCount = Period_AutoByCount.Value;
                        //
                        periodToUse = new TimeSpan((End.Value - Start.Value).Ticks / divisionCount);
                    }
                    else
                    {
                        periodToUse = Period_Custom.Value;
                    }
                    //
                    d1 = (End == null) ? nowUTC : End.Value;
                    while (((MaxCaseCount == null && (Start != null || cCase <= 10)) || (MaxCaseCount != null && cCase <= MaxCaseCount.Value)) && (Start == null || d1 >= Start.Value))
                    {
                        d2 = d1.Subtract(periodToUse);
                        periods.Insert(0, new DataStructure.Pair<DateTime?, DateTime?>(d2, d1));
                        d1 = d2;
                        cCase++;
                    }
                }
                //
                if (periods.Count > 0)
                {
                    if (AutoInfinity_Start)
                        periods.Insert(0, new DataStructure.Pair<DateTime?, DateTime?>(null, (from x1 in periods.Where(q => q.Data1 != null) select x1.Data1.Value).Min()));
                    if (AutoInfinity_End)
                        periods.Add(new DataStructure.Pair<DateTime?, DateTime?>((from x1 in periods.Where(q => q.Data2 != null) select x1.Data2.Value).Max(), null));
                    //
                    int iCase = 1, iParam = 1;
                    string sParamName, sPointText, sPointText_Start, sPointText_End;

                    if (casesTexts == null)
                        casesTexts = new Dictionary<int, string>();
                    casesTexts.Clear();
                    s1 = "(CASE \n";
                    foreach (var fe1 in from x1 in periods
                                        select x1)
                    {
                        listHelper1 = new DataStructure.ListHelper<string>();
                        if (fe1.Data1 != null)
                        {
                            sParamName = string.Format("@{0}myP_{1}", prefixForNames, iParam);
                            iParam++;
                            r.Params.Add(string.Format("{0} DATETIME = {1}", sParamName, string.Format("'{0}'", fe1.Data1.Value.ToString("yyyy-MM-ddTHH:mm:ss.fff"))));
                            listHelper1.Source.Add(string.Format("({0}) > {1}", colSQL, sParamName));
                        }
                        if (fe1.Data2 != null)
                        {
                            sParamName = string.Format("@{0}myP_{1}", prefixForNames, iParam);
                            iParam++;
                            r.Params.Add(string.Format("{0} DATETIME = {1}", sParamName, string.Format("'{0}'", fe1.Data2.Value.ToString("yyyy-MM-ddTHH:mm:ss.fff"))));
                            listHelper1.Source.Add(string.Format("({0}) <= {1}", colSQL, sParamName));
                        }
                        s1 += string.Format("\tWHEN ({0}) THEN {1} \n", listHelper1.ToSeperated(" AND "), iCase);
                        //
                        sPointText_Start = "";
                        if (fe1.Data1 != null)
                        {
                            if (PointText_Converted_Calendar == null)
                            {
                                if (string.IsNullOrEmpty(PointText_NotConverted_FormatString))
                                    sPointText_Start = fe1.Data1.Value.ToString();
                                else
                                    sPointText_Start = fe1.Data1.Value.ToString(PointText_NotConverted_FormatString);
                            }
                            else
                            {
                                sPointText_Start = new DateTimeHelper(PointText_Converted_Calendar.Value, fe1.Data1, DateTimeKind.Local).ToFormatedString(PointText_Converted_Start_Format, PointText_Converted_Start_IncludeTime);
                            }
                        }
                        //
                        sPointText_End = "";
                        if (fe1.Data2 != null)
                        {
                            if (PointText_Converted_Calendar == null)
                            {
                                if (string.IsNullOrEmpty(PointText_NotConverted_FormatString))
                                    sPointText_End = fe1.Data2.Value.ToString();
                                else
                                    sPointText_End = fe1.Data2.Value.ToString(PointText_NotConverted_FormatString);
                            }
                            else
                            {
                                sPointText_End = new DateTimeHelper(PointText_Converted_Calendar.Value, fe1.Data2, DateTimeKind.Local).ToFormatedString(PointText_Converted_End_Format, PointText_Converted_End_IncludeTime);
                            }
                        }
                        //
                        sPointText = "";
                        switch (PointText_DisplayType)
                        {
                            case ChartDataGeneratorAxisDateTimePointTextDisplayTypes.Period:
                                if (!string.IsNullOrEmpty(sPointText_Start) && !string.IsNullOrEmpty(sPointText_End))
                                    sPointText = string.Format("{0} - {1}", sPointText_Start, sPointText_End);
                                else if (!string.IsNullOrEmpty(sPointText_Start))
                                    sPointText = sPointText_Start;
                                else
                                    sPointText = sPointText_End;
                                break;
                            case ChartDataGeneratorAxisDateTimePointTextDisplayTypes.Start:
                                sPointText = sPointText_Start;
                                break;
                            case ChartDataGeneratorAxisDateTimePointTextDisplayTypes.End:
                            default:
                                sPointText = sPointText_End;
                                break;
                        }
                        casesTexts.Add(iCase, sPointText);
                        //
                        iCase++;
                    }
                    //
                    s1 += "END)";
                    r.ColumnsSQLs.Add(columnValueName, s1);
                }
            }
            else
            {
                int iColumn = 0;
                string sColumnName;
                string sConvertToLocal = (Period_AutoByDatePortion_ConvertToLocalDate) ? "1" : "0";
                colWithDayOfWeekValue = null;
                //
                foreach (ChartDataGeneratorAxisDateTimeDatePortions fe1 in Period_AutoByDatePortions)
                {
                    if (fe1 == ChartDataGeneratorAxisDateTimeDatePortions.Year)
                    {
                        iColumn++;
                        sColumnName = string.Format(columnValueName, iColumn);
                        if (PointText_Converted_Calendar == null || PointText_Converted_Calendar.Value == DateTimeHelperCalendars.Persian)
                            s1 = string.Format("{0}({1},{2})", "[dbo].[Year_Persian]", colSQL, sConvertToLocal);
                        else
                            s1 = string.Format("{0}({1})", "YEAR", colSQL);
                        //s1 = string.Format("(\n\t{0}\n) AS {1}", s1, sColumnName);
                        s1 = string.Format("(\n\t{0}\n)", s1);
                        r.ColumnsSQLs.Add(sColumnName, s1);
                    }
                    else if (fe1 == ChartDataGeneratorAxisDateTimeDatePortions.Month)
                    {
                        iColumn++;
                        sColumnName = string.Format(columnValueName, iColumn);
                        if (PointText_Converted_Calendar == null || PointText_Converted_Calendar.Value == DateTimeHelperCalendars.Persian)
                            s1 = string.Format("{0}({1},{2})", "[dbo].[Month_Persian]", colSQL, sConvertToLocal);
                        else
                            s1 = string.Format("{0}({1})", "MONTH", colSQL);
                        //s1 = string.Format("(\n\t{0}\n) AS {1}", s1, sColumnName);
                        s1 = string.Format("(\n\t{0}\n)", s1);
                        r.ColumnsSQLs.Add(sColumnName, s1);
                    }
                    else if (fe1 == ChartDataGeneratorAxisDateTimeDatePortions.Day)
                    {
                        iColumn++;
                        sColumnName = string.Format(columnValueName, iColumn);
                        if (PointText_Converted_Calendar == null || PointText_Converted_Calendar.Value == DateTimeHelperCalendars.Persian)
                            s1 = string.Format("{0}({1},{2})", "[dbo].[Day_Persian]", colSQL, sConvertToLocal);
                        else
                            s1 = string.Format("{0}({1})", "DAY", colSQL);
                        //s1 = string.Format("(\n\t{0}\n) AS {1}", s1, sColumnName);
                        s1 = string.Format("(\n\t{0}\n)", s1);
                        r.ColumnsSQLs.Add(sColumnName, s1);
                    }
                    else if (fe1 == ChartDataGeneratorAxisDateTimeDatePortions.DayOfWeek)
                    {
                        iColumn++;
                        sColumnName = string.Format(columnValueName, iColumn);
                        if (PointText_Converted_Calendar == null || PointText_Converted_Calendar.Value == DateTimeHelperCalendars.Persian)
                        {
                            s1 = string.Format("{0}({1},{2})", "[dbo].[DayOfWeek_Persian]", colSQL, sConvertToLocal);
                            colWithDayOfWeekValue = iColumn;
                        }
                        else
                            s1 = string.Format("{0}{1})", "DATENAME(WEEKDAY, ", colSQL);
                        //s1 = string.Format("(\n\t{0}\n) AS {1}", s1, sColumnName);
                        s1 = string.Format("(\n\t{0}\n)", s1);
                        r.ColumnsSQLs.Add(sColumnName, s1);
                    }
                }
            }
            //
            return r;
        }

        public override string GetPointText(List<object> pointValues)
        {
            if (PeriodType != ChartDataGeneratorAxisDateTimePeriodTypes.AutoByDatePortions)
            {
                if (pointValues != null && pointValues.Count == 1 && pointValues[0] is int)
                {
                    int pointIndex = (int)pointValues[0];
                    if (casesTexts.ContainsKey(pointIndex))
                        return casesTexts[pointIndex];
                    else
                        return null;
                }
                else
                    return null;
            }
            else
            {
                if (colWithDayOfWeekValue != null && colWithDayOfWeekValue.Value < pointValues.Count)
                    pointValues[colWithDayOfWeekValue.Value] = DateTimeHelper.GetDayOfWeekName(DateTimeHelperCalendars.Persian, (DayOfWeek)pointValues[colWithDayOfWeekValue.Value]);
                return base.GetPointText(pointValues);
            }
        }

        public override void CopySettingsFrom(ChartDataGeneratorAxis other)
        {
            if (other == null)
                return;
            if (other is ChartDataGeneratorAxisDateTime)
            {
                ChartDataGeneratorAxisDateTime otherTyped = other as ChartDataGeneratorAxisDateTime;
                this.AutoInfinity_End = otherTyped.AutoInfinity_End;
                this.AutoInfinity_Start = otherTyped.AutoInfinity_Start;
                this.End = otherTyped.End;
                this.MaxCaseCount = (otherTyped.MaxCaseCount == null) ? null : (int?)otherTyped.MaxCaseCount.Value; ;
                this.Period_AutoByCount = otherTyped.Period_AutoByCount;
                this.Period_AutoByDatePortion_ConvertToLocalDate = otherTyped.Period_AutoByDatePortion_ConvertToLocalDate;
                this.Period_AutoByDatePortions = otherTyped.Period_AutoByDatePortions;
                this.Period_Custom = otherTyped.Period_Custom;
                this.PeriodType = otherTyped.PeriodType;
                this.PointText_Converted_Calendar = otherTyped.PointText_Converted_Calendar;
                this.PointText_Converted_End_Format = otherTyped.PointText_Converted_End_Format;
                this.PointText_Converted_End_IncludeTime = otherTyped.PointText_Converted_End_IncludeTime;
                this.PointText_Converted_Start_Format = otherTyped.PointText_Converted_Start_Format;
                this.PointText_Converted_Start_IncludeTime = otherTyped.PointText_Converted_Start_IncludeTime;
                this.PointText_DisplayType = otherTyped.PointText_DisplayType;
                this.PointText_NotConverted_FormatString = otherTyped.PointText_NotConverted_FormatString;
                this.PointTextSeperator = otherTyped.PointTextSeperator;
                this.Start = otherTyped.Start;
            }
        }
        public override void CopyTo(ChartDataGeneratorAxis target)
        {
            if (target == null)
                return;
            base.CopyTo(target);
            if (target is ChartDataGeneratorAxisDateTime)
            {
                ChartDataGeneratorAxisDateTime target_Typed = target as ChartDataGeneratorAxisDateTime;
                target_Typed.ColumnDesignId = this.ColumnDesignId;
                target_Typed.ColumnSQL = this.ColumnSQL;
                target_Typed.CopySettingsFrom(this);
            }
        }
        public override object Clone()
        {
            ChartDataGeneratorAxisDateTime itemNew = new ChartDataGeneratorAxisDateTime();
            CopyTo(itemNew);
            return itemNew;
        }
    }
}
